package cc.shacocloud.mirage.utils.annotation;

import cc.shacocloud.mirage.utils.KotlinDetector;
import cc.shacocloud.mirage.utils.collection.ArrayUtil;
import cc.shacocloud.mirage.utils.map.ConcurrentReferenceHashMap;
import cc.shacocloud.mirage.utils.reflection.ReflectUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.function.Predicate;

/**
 * 注解工具类
 */
public class AnnotatedElementUtils {
    
    /**
     * 元注解
     */
    static final Set<Class<? extends Annotation>> META_ANNOTATIONS = new HashSet<>(Arrays.asList(
            Target.class,
            Retention.class,
            Inherited.class,
            Documented.class,
            SuppressWarnings.class,
            Override.class,
            Deprecated.class
    
    ));
    
    private static final Map<AnnotatedElement, AnnotatedElementMetadata> annotationElementCache =
            new ConcurrentReferenceHashMap<>(256);
    
    /**
     * 是否为元注解，即 jdk 或 kotlin 定义的元注解
     *
     * @param annotationType 注解类型
     * @return 是否为元注解
     */
    public static boolean isMetaAnnotation(Class<? extends Annotation> annotationType) {
        if (KotlinDetector.isKotlinType(annotationType)) {
            return KotlinDelegate.isMetaAnnotation(annotationType);
        }
        return META_ANNOTATIONS.contains(annotationType);
    }
    
    /**
     * 是否不为元注解。即 jdk 或 kotlin 定义的元注解
     *
     * @param annotationType 注解类型
     * @return 是否为元注解
     */
    public static boolean isNotMateAnnotation(Class<? extends Annotation> annotationType) {
        return !isMetaAnnotation(annotationType);
    }
    
    /**
     * 将指定的被注解的元素转换为组合注解元素
     *
     * @param annotationEle 注解元素
     * @return 组合注解元素
     */
    @Contract("null -> new")
    public static @NotNull AnnotatedElementMetadata getAnnotatedMetadata(AnnotatedElement annotationEle) {
        if (annotationEle instanceof AnnotatedElementMetadata) {
            return (AnnotatedElementMetadata) annotationEle;
        }
        
        return annotationElementCache.computeIfAbsent(annotationEle, CombinationAnnotationElement::new);
    }
    
    /**
     * 获取指定注解
     *
     * @param annotationEle   {@link AnnotatedElement}，可以是Class、Method、Field、Constructor、ReflectPermission
     * @param isToCombination 是否为转换为组合注解，组合注解可以递归获取注解的注解
     * @return 注解对象
     */
    public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination) {
        return getAnnotations(annotationEle, isToCombination, null);
    }
    
    /**
     * 获取指定注解
     *
     * @param annotationEle   {@link AnnotatedElement}，可以是Class、Method、Field、Constructor、ReflectPermission
     * @param isToCombination 是否为转换为组合注解，组合注解可以递归获取注解的注解
     * @param predicate       过滤器，{@link Predicate#test(Object)}返回{@code true}保留，否则不保留
     * @return 注解对象，如果提供的{@link AnnotatedElement}为{@code null}，返回{@code null}
     */
    public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Predicate<Annotation> predicate) {
        if (null == annotationEle) {
            return null;
        }
        
        Annotation[] annotations;
        if (isToCombination) {
            annotations = getAnnotatedMetadata(annotationEle).getAnnotations();
        } else {
            annotations = annotationEle.getAnnotations();
        }
        
        if (null == predicate) {
            return annotations;
        }
        
        return ArrayUtil.filter(annotations, predicate);
    }
    
    /**
     * 获取指定注解
     *
     * @param <A>            注解类型
     * @param annotationEle  {@link AnnotatedElement}，可以是Class、Method、Field、Constructor、ReflectPermission
     * @param annotationType 注解类型
     * @return 注解对象
     */
    @Nullable
    public static <A extends Annotation> A getAnnotation(AnnotatedElement annotationEle, Class<A> annotationType) {
        return (null == annotationEle) ? null : getAnnotatedMetadata(annotationEle).getAnnotation(annotationType);
    }
    
    /**
     * 检查是否包含指定注解指定注解
     *
     * @param annotationEle  {@link AnnotatedElement}，可以是Class、Method、Field、Constructor、ReflectPermission
     * @param annotationType 注解类型
     * @return 是否包含指定注解
     */
    public static boolean hasAnnotation(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) {
        return Objects.nonNull(getAnnotation(annotationEle, annotationType));
    }
    
    /**
     * 获取指定注解属性的值<br>
     * 如果无指定的属性方法返回null
     *
     * @param <T>            注解值类型
     * @param annotationEle  {@link AnnotatedElement}，可以是Class、Method、Field、Constructor、ReflectPermission
     * @param annotationType 注解类型
     * @param attributeName  属性名，例如注解中定义了name()方法，则 此处传入name
     * @param type           属性类型
     * @return 注解对象
     */
    public static <T> Optional<T> getAnnotationValue(AnnotatedElement annotationEle,
                                                     Class<? extends Annotation> annotationType,
                                                     String attributeName,
                                                     Class<T> type) {
        return getAnnotatedMetadata(annotationEle).getValue(annotationType, attributeName, type);
    }
    
    /**
     * 获取指定注解中所有属性值
     * <p>
     * 如果无指定的属性方法返回null
     *
     * @param annotationEle  {@link AnnotatedElement}，可以是Class、Method、Field、Constructor、ReflectPermission
     * @param annotationType 注解类型
     * @return 注解对象
     */
    @Nullable
    public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotationEle,
                                                               Class<? extends Annotation> annotationType) {
        return getAnnotatedMetadata(annotationEle).getAnnotationAttributes(annotationType);
    }
    
    /**
     * 获取注解类的保留时间，可选值 SOURCE（源码时），CLASS（编译时），RUNTIME（运行时），默认为 CLASS
     *
     * @param annotationType 注解类
     * @return 保留时间枚举
     */
    public static RetentionPolicy getRetentionPolicy(@NotNull Class<? extends Annotation> annotationType) {
        final Retention retention = annotationType.getAnnotation(Retention.class);
        if (null == retention) {
            return RetentionPolicy.CLASS;
        }
        return retention.value();
    }
    
    /**
     * 获取注解类可以用来修饰哪些程序元素，如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等
     *
     * @param annotationType 注解类
     * @return 注解修饰的程序元素数组
     */
    public static ElementType[] getTargetType(@NotNull Class<? extends Annotation> annotationType) {
        final Target target = annotationType.getAnnotation(Target.class);
        if (null == target) {
            return new ElementType[]{ElementType.TYPE, //
                    ElementType.FIELD, //
                    ElementType.METHOD, //
                    ElementType.PARAMETER, //
                    ElementType.CONSTRUCTOR, //
                    ElementType.LOCAL_VARIABLE, //
                    ElementType.ANNOTATION_TYPE, //
                    ElementType.PACKAGE//
            };
        }
        return target.value();
    }
    
    /**
     * 是否会保存到 Javadoc 文档中
     *
     * @param annotationType 注解类
     * @return 是否会保存到 Javadoc 文档中
     */
    @Contract(pure = true)
    public static boolean isDocumented(@NotNull Class<? extends Annotation> annotationType) {
        return annotationType.isAnnotationPresent(Documented.class);
    }
    
    /**
     * 是否可以被继承，默认为 false
     *
     * @param annotationType 注解类
     * @return 是否会保存到 Javadoc 文档中
     */
    @Contract(pure = true)
    public static boolean isInherited(@NotNull Class<? extends Annotation> annotationType) {
        return annotationType.isAnnotationPresent(Inherited.class);
    }
    
    /**
     * 设置新的注解的属性（字段）值
     *
     * @param annotation      注解对象
     * @param annotationField 注解属性（字段）名称
     * @param value           要更新的属性值
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void setValue(Annotation annotation, String annotationField, Object value) {
        final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues");
        memberValues.put(annotationField, value);
    }
    
    /**
     * 方法是否为注解属性方法。 <br>
     * 方法无参数，且有返回值的方法认为是注解属性的方法。
     *
     * @param method 方法
     */
    static boolean isAttributeMethod(@NotNull Method method) {
        return method.getParameterCount() == 0 && method.getReturnType() != void.class;
    }
    
    /**
     * 内部类，以避免在运行时对 Kotlin 的硬依赖
     */
    private static class KotlinDelegate {
        
        /**
         * 元注解
         */
        static final Set<Class<? extends Annotation>> META_ANNOTATIONS = new HashSet<>(Arrays.asList(
                kotlin.annotation.Target.class,
                kotlin.annotation.Retention.class,
                kotlin.annotation.MustBeDocumented.class,
                kotlin.annotation.Repeatable.class,
                kotlin.Metadata.class
        ));
        
        /**
         * 是否为元注解
         *
         * @param annotationType 注解类型
         * @return 是否为 kotlin 的元注解
         */
        public static boolean isMetaAnnotation(Class<? extends Annotation> annotationType) {
            return META_ANNOTATIONS.contains(annotationType);
        }
    }
    
}
